fix: stop mail loops from bounces and autoresponders - #3628
Merged
Conversation
Inbound mail was only screened with the X-Auto-Generated header, which only helpdesk's own acks set. A real bounce or out-of-office therefore reached ticket creation and could start three separate loops: the ack in HD Ticket.after_insert replies to raised_by (the address that just bounced), threading onto a portal ticket makes frappe CC the parent doc's owner on every inbound mail, and an account with enable_auto_reply answers mailer-daemon directly. Detect machine-generated mail by the standard markers instead: RFC 3834 Auto-Submitted, the RFC 3464 multipart/report delivery-status type, and the RFC 5321 null return-path. Matches are routed to handle_bad_emails so they land in Unhandled Email rather than disappearing -- the fetch has already marked them seen either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #3628 +/- ##
===========================================
+ Coverage 68.57% 74.30% +5.72%
===========================================
Files 140 149 +9
Lines 9181 11082 +1901
===========================================
+ Hits 6296 8234 +1938
+ Misses 2885 2848 -37 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
RitvikSardana
marked this pull request as ready for review
July 30, 2026 10:53
|
Tick the box to add this pull request to the merge queue (same as
|
Contributor
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Reviews (7): Last reviewed commit: "chore: shorten comments" | Re-trigger Greptile |
Member
Author
|
@RitvikSardana have to test one more thing, do not merge yet |
aerodeval
marked this pull request as draft
August 7, 2026 13:02
Record Unhandled Email on every transport, not just IMAP, and leave an internal comment on the matched ticket when a bounce or auto-reply is parked, so agents still see failed deliveries and out-of-office replies.
aerodeval
marked this pull request as ready for review
September 2, 2026 20:12
An out of office should reach agents on the ticket, and auto-replied senders rate-limit themselves so they cannot sustain a loop the way bounces and auto-generated feeds can. Those still get parked.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Inbound mail is only screened with the
X-Auto-Generatedheader, which nothing but helpdesk's own acknowledgement emails set. Real bounces and out-of-office replies pass straight through into ticket creation, where they can start a mail loop with the sending MTA.Once a machine-generated mail becomes a ticket or a communication, three separate paths can reply to it:
HD Ticket.after_insertsends the acknowledgement toraised_by, i.e. the address that just bounced.mail_cc) on every inbound mail — the same dead address.enable_auto_reply— an account with auto-reply on answersmailer-daemondirectly.Each reply bounces again, which creates another inbound mail, and so on.
Changes
All in
helpdesk/overrides/email_account.py:auto_generated_reason(msg)identifies machine-generated mail by the standard markers instead of relying on our own header. Checked in this order:X-Auto-GeneratedContent-Type: multipart/report; report-type=delivery-statusReturn-Path: <>(null envelope sender)Auto-Submitted: auto-generated(or any value other thanno/auto-replied)Auto-Submitted: auto-repliedAuto-Submitted: no(parameters tolerated, e.g.no; owner=...)Why
auto-repliedpasses: an out-of-office should reach agents on the ticket, and it cannot sustain a loop the way a bounce can — it only threads (no new ticket, so no acknowledgement goes out), and auto-responders rate-limit themselves to one reply per sender. Bounces andauto-generatedfeeds have no such limit, which is why they stay parked.The bounce markers are checked before
Auto-Submitted, so a DSN that also carriesAuto-Submitted: auto-replied(Gmail's do) is still parked, with the recorded reason naming the dead address rather than a mere autoresponder.Parked, not dropped. Matches go to
handle_bad_emails(uid, message, reason)and land in Unhandled Email with the reason recorded, instead of being silently dropped by a barecontinue. The framework version ofhandle_bad_emailsonly records for IMAP accounts, so it is overridden without theuse_imapgate — POP3 and Frappe Mail drops leave the same trace, and a misclassified customer mail is always recoverable.Agents still see what happened. Parking means bounces no longer thread onto tickets — an agent would never learn their reply went nowhere. When a parked mail belongs to an existing ticket (same
In-Reply-To/Referenceslookup the threading code uses),notify_ticket_of_parked_mailleaves an internal comment there by Administrator:Final-Recipientwhen present)auto-generatedreply → "Auto-reply received from postmaster@example.com."Comments notify nobody (no @mention), customers never see them, and a comment failure only logs — it cannot break the mail pull. No dedupe: one comment per parked mail, naturally capped at one per reply an agent sends.
Deliberate behavior notes:
auto-repliedis parked, includingAuto-Submitted: auto-generatedalert mail (monitoring systems, cron jobs). Any auto-generated sender is one sloppy autoresponder away from a loop with our acknowledgement, and a misfiring monitor floods the drawer instead of the ticket queue. If alert-to-ticket is ever needed, it should be an explicit account setting in a follow-up.Testing
helpdesk/overrides/test_email_account.py— 15 tests:auto_generated_reasonand_failed_recipientwith real message fixtures (Gmail DSN with amessage/delivery-statuspart, out-of-office kept, quarantine alert parked, a DSN with noReturn-Path, genuine customer replies, theAuto-Submitted: nocases).Also verified end to end on a dev site by driving
get_inbound_mailswith a stubbed mail client: the bounce is parked with reasondelivery status notificationand the ticket gets the delivery-failed comment with the failed address; the genuine customer reply in the same batch is kept.Screenshots
The agent's reply bounces and the customer's out-of-office arrives, on the same ticket. The bounce is parked and leaves the internal comment; the out-of-office threads like a normal reply. Before this PR the bounce would have threaded in as a fake customer reply and started the loop; agents saw nothing about the failed delivery.